﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Outfish {

	/// <summary>
	/// Base class for parsing and validating web pages.
	/// </summary>
	/// <remarks>
	/// Derived classes should implement a BuildDocument to provide access to an organized document tree.
	/// </remarks>
	public class WebPage {
	
		#region constructor
		
		/// <summary>
		/// Constructs a WebPage that needs its content loaded via LoadContent()
		/// </summary>
		public WebPage(){}
		
		/// <summary>
		/// constructs a WebPage for the given html content.
		/// Does NOT call derived classes LoadContent() method
		/// </summary>
		/// <param name="content">html string</param>
		public WebPage(string source) {
			this.LoadContent(source);
		}
		
		#endregion
		
		/// <summary>
		/// Returns the original string used to construct the page.
		/// </summary>
		public string Source{ 
			get{ 
				if( this._source == null ){ throw new NullReferenceException("Content was never initialized."); }
				return this._source; 
			} 
			protected set{
				this._source = value;
			}
		}

		/// <summary>
		/// Loads the source string into the document. 
		/// Override this to perform custom parsing in derived classes.
		/// </summary>
		/// <param name="content"></param>
		virtual public void LoadContent(string source){
			if( this._source != null ){ throw new InvalidOperationException("Source is already loaded.  Cannot reload."); }
			if( source == null ){ throw new ArgumentNullException("source","Cannot initialize WebPage.Source to null."); }
			this._source = source;
		}

		/// <summary>Derived classes define how to load document. Only called once per document</summary>
		virtual protected WebDocument BuildDocument(){
			string msg = "Unable to build document for generic WebPage.  Use derived type such as HtmlPage or JavaScriptPage.";
			throw new InvalidOperationException( msg );
		}

		#region public convenience Regex methods

		/// <summary>
		/// Search the page content for the first match of the regular expression
		/// </summary>
		/// <returns>null if no match is found.</returns>
		public Match Match( string regexPattern ){
			Match m = Regex.Match( this.Source, regexPattern );
			return m.Success ? m : null;
		}

		/// <summary>
		/// Search the html content for all matches of the regulat expression
		/// </summary>
		public IEnumerable<Match> Matches( string regexPattern ){
			return this.Matches( regexPattern, RegexOptions.None );
		}

		/// <summary>
		/// Search the html content for all matches of the regulat expression
		/// </summary>
		public IEnumerable<Match> Matches( string regexPattern, RegexOptions options ){
			return Regex.Matches( this.Source, regexPattern, options )
				.Cast<Match>();
		}

		#endregion

		/// <summary>The Document Object Model for this page.</summary>
		/// <remarks>Lazy loaded so not generated if never used.</remarks>
		public WebDocument Document{ 
			get{
				if( this._document == null ){
					this._document = this.BuildDocument();
				}
				return this._document;
			} 
		}
	
		/// <summary>
		/// Gets Root Node of Document Object Model (DOM)
		/// </summary>
		public HtmlNode Root{ get{ return this.Document.DocumentNode; } }

		#region Equals and GetHashCode implementation
		
		public override int GetHashCode() { return this.Source.GetHashCode(); }

		public override bool Equals(object obj) { return this.Equals( obj as WebPage ); }

		public bool Equals( WebPage page ){ return page!=null && page.Source == this.Source; }
		
		#endregion

		#region private fields

		WebDocument _document; // lazy loaded when/if it is neede

		/// <summary>stores the html string</summary>
		/// <remarks>while this is null, web page is an inconsistent state.</remarks>
		string _source;
		
		#endregion

	}
	
	
}
